// Lighting Pass
// Pixel Shader
//------------------------------------------

// Defines.
//------------------------------------------
#define PI 3.14159265359

// Typedefs.
//------------------------------------------
struct Pixel
{
    float4 position : SV_POSITION;
	float2 uv       : TEXCOORD0;
};

// Globals.
//------------------------------------------
cbuffer LightBuffer
{
	float4 l_position;
	float3 l_direction;
	float1 l_angle;
	float1 l_radius;
	float3 l_ambient;
	float3 l_diffuse;
	float1 l_constant_attenuation;
	float3 l_specular;
	float1 l_linear_attenuation;
	float1 l_quadratic_attenuation;
	float1 l_exponent;
	float2 l_padding;
}

Texture2D g_world_positions;
Texture2D g_world_view_directions;
Texture2D g_normals;
Texture2D g_ambient;
Texture2D g_diffuse;
Texture2D g_specular;
Texture2D g_shininess;

SamplerState g_sampler
{
	MinFilter = None;
	MagFilter = None;
	MipFilter = None;
};

float4 Main(Pixel input) : SV_TARGET
{
	float3 result = float3(0.0f, 0.0f, 0.0f);

	float3 ray;
	float1 attenuation = 1.0f;
	
	// Fetch data from GBuffer.
	float3 world_position = g_world_positions.Sample(g_sampler, input.uv).xyz;
	float3 world_view_direction = g_world_view_directions.Sample(g_sampler, input.uv).xyz;
	float3 normal = g_normals.Sample(g_sampler, input.uv).xyz;
	float3 m_ambient = g_ambient.Sample(g_sampler, input.uv).rgb;
	float3 m_diffuse = g_diffuse.Sample(g_sampler, input.uv).rgb;
	float3 m_specular = g_specular.Sample(g_sampler, input.uv).rgb;
	float1 m_shininess = g_shininess.Sample(g_sampler, input.uv).x;
	
	// Check for directional light.
	if(l_position.w == 0.0f)
	{
		// Normalize light direction.
		ray = normalize(l_direction.xyz);
	}
	// Otherwise it's a point or spot light.
	else
	{
		// Get the light direction and normalize it.
		ray = world_position - l_position.xyz;
		ray = normalize(ray);
		
		// Compute the attenuation.
		float1 distance = length(world_position - l_position.xyz);
		
		// Outside range.
		if(distance > l_radius)
			return float4(result, 1);
		
		attenuation = 1.0f /
					  (l_constant_attenuation +
					   l_linear_attenuation * distance +
					   l_quadratic_attenuation * pow(distance, 2));
		
		// It's a spot light.
		if(l_angle <= PI / 2.0f)
		{
			float1 spot_dot = max(0.0f, dot(normalize(l_direction), ray));
			float1 cos_angle = cos(l_angle);
			
			// Outside of spot cone.
			if(spot_dot < cos_angle)	
				return float4(result, 1);
			
			// Soft spot edges.
			attenuation *= pow((spot_dot - cos_angle) / (1- cos_angle), l_exponent);
		} 	
	}
	
	// Specular reflexion.		
	float1 incidence = max(0.0f, dot(normal, -ray.xyz));
	float1 specular_power = 0.0f;

	if(incidence > 0.0f)
	{
		float3 reflexion = normalize(reflect(ray, normal));
		specular_power = pow(saturate(dot(reflexion, world_view_direction)), m_shininess);
	}
	
	// Compute result.
	result = attenuation * m_ambient * l_ambient +
			 attenuation * incidence * m_diffuse * l_diffuse +
			 attenuation * specular_power * m_specular * l_specular;

		return float4(result, 1);
}